<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title></title>
<style type="text/css">
*{
	margin: 0;
	padding: 0;
}
html,body{
	width: 100%;
	height: 100%;
	background-color: #000;
	color: #fff;
	overflow: hidden;
	text-align: center;
}
.title{
	font-size: 30px;
	font-weight: bold;
	margin: 30px;
}
.game-container{
	position: absolute;
	left: 50%;
	top: 0;
	transform: translate(-50%,0);
	width: fit-content;
	height: fit-content;
	z-index: 3;
}
#gameCanvas{
	border: 10px solid #646464;
	margin: 10px;
	position: relative;
	z-index: 1;
}
.score{
	margin: 0 auto;
	width: fit-content;
	padding: 4px 20px;
	border: 1px solid #fff;
}
.ui{
	display: flex;
	margin-top: 10px;
}
.ui .btn{
	padding: 2px 20px;
	border-radius: 10px;
	border: 1px solid #fff;
	margin: 10px;
	user-select: none;
	cursor: pointer;
}
.ui .btn:hover{
	background-color: #ffffff44;
}
.tip{
	text-align: left;
	text-indent: 1em;
	color: #bbb;
	font-size: 12px
}
#maskEl{
	position: fixed;
	left: 0;
	top: 0;
	width: 100%;
	height: 100%;
	background-color: #00000099;
	z-index: 2;
}
.mask{
	position: fixed;
	left: 50%;
	top: 50%;
	transform: translate(-50%,-50%);
	width: 200px;
	max-height: 200px;
	overflow-y: auto;
	background-color: #1a2023aa;
	padding: 20px 0;
}
.mask::-webkit-scrollbar{
	width: 6px;
	box-shadow: 0 0 4px #00000088;
}
.mask::-webkit-scrollbar-thumb{
	background-color: #cecece;
	border-radius: 5px;
}
.mask::-webkit-scrollbar-track{
	background-color: #8b8b8b;
}
.mask .m-title{
	margin-bottom: 20px;
	font-size: 20px;
}
#cmd{
	margin-top: 10px;
	width: 800px;
	height: 100px;
	border: 1px solid #fff;
	border-radius: 10px;
}
#myCanvas{
	position: fixed;
	left: 0;
	top: 0;
	z-index: 1;
}
</style>
</head>
<body>
<div class="game-container">
	<div class="title">贪吃蛇</div>
	<canvas id="gameCanvas" width="400" height="400"></canvas>
	<div class="score">得分：<span id="score">0</span></div>
	<div class="ui">
		<div class="btn" onclick="start(true)">重新开始</div>
		<div class="btn" id="pauseBtn" onclick="pause()">暂停</div>
		<div class="btn" onclick="showRecord()">查看记录</div>
	</div>
	<div class="tip">ADWS操作</div>
	<div style="font-size: 18px;">信息框</div>
	<div id="cmd" style="color: #f40;text-align: left;padding: 10px;"></div>
	<div id="maskEl" style="display:none;">
		<div class="mask">
			<div class="m-title">我的记录</div>
			<div id="record">
				空
			</div>
		</div>
	</div>
</div>
<canvas id="myCanvas"></canvas>
<script type="text/javascript">
!function (e,w){
	function Particle(option) {
		this.originOption = {
			el: "#myCanvas",// canvas 选择器 ( 可配置 )
			f: 1200, // 焦距 ( 可配置 )
			dis: 14000, // 视距 ( 可配置 )
			rY: 0, // 初始 y 轴旋转角度 ( 可配置 )
			rX: 0, // 初始 x 轴旋转角度 ( 可配置 )
			mode: 5, // 模式 ( 可配置 )
			preEmit: false, // 是否预运行 ( 可配置 )
			draggable: true, // 是否允许拖拽 ( 可配置 )
			pList: [], // 集中管理粒子 ( 可配置 )
			lightList: [], // 灯光队列
			mainWidth: w.innerWidth, // 画布宽
			mainHeight: w.innerHeight, // 画布高
			canvas: null, // canvas dom
			cx: null, // canvas2d 上下文对象
			emitterPos: [400, 400], // 发射器坐标
			nPs: 20, // 每秒发射的粒子数量
			timer: null, // 动画计时器
			cTimer: null, // 创建粒子计时器
			life: 5000, // 粒子生命 (ms)
			ms: 0, // 当前时间
			dur: 10, // 发射粒子间隔(需初始化)
			isPress: false, // 鼠标是否按下
			dragX: 0, // x 方向拖拽量
			dragY: 0, // y 方向拖拽量
			_currX: 0, // x 方向临时拖拽量
			_currY: 0 ,// y 方向临时拖拽量
			swing: 50, // 全局振幅
			freq: 1, // 全局振频
			userDis: false, // 用户是否设置视距
			userN: false, // 用户是否设置横纵数量
			zoomQuotiety: 30, // 滚轮缩放速度
			drawFace: true, // 是否绘制面(tCubic模式)
			drawSide: false, // 是否绘制线框(tCubic模式)
			drawAnchor: false ,// 是否绘制顶点(tCubic模式)
			glow: false,
			showTail: false, // 是否开启拖尾模式
		}
		Object.assign(this, this.originOption, option)
		;option.dis && (this.userDis = true)
		;(option.nX || option.nY) && (this.userN = true)
		this.init()
	}
	Particle.prototype.expend = function(obj) {Object.assign(this, obj)}
	Particle.prototype.expend({
		init() {
			this.dur = Math.floor(1000 / this.nPs) // 初始化粒子发射时间间隔
			this.dragY = this.rX * 180 / Math.PI
			this.dragX = this.rY * 180 / Math.PI
			this.canvas = e.querySelector(this.el)
			if(!this.canvas){
				throw Error("Please provide an enable canvas element selector for option 'el'")
				return false
			}
			if(this.canvas.isUsed){
				throw Error("Reduplicate canvas element, please provide another enable canvas element selector for option 'el', or use function 'destroy()' to remove this instance first!")
				return false
			}
			this.cx = this.canvas.getContext("2d")
			this.canvas.setAttribute("width",this.mainWidth)
			this.canvas.setAttribute("height",this.mainHeight)
			this.showTail && (this.canvas.style.mixBlendMode = "screen")
			this.canvas.isUsed = true // 占用
			this.emitterPos = [this.mainWidth / 2, this.mainHeight / 2]
			this.addEvent()
			this.emit()
		},
		addEvent() {
			this.windowMousedown = () => {
				if(this.draggable){
					this.isPress = true
					this._currX = event.pageX + this.dragX
					this._currY = event.pageY / 2 + this.dragY
				}
			}
			this.windowMousemove = () => {
				if (this.isPress && this.draggable) {
					this.dragX = this._currX - event.pageX
					this.dragY = this._currY - event.pageY / 2
					this.rY = this.dragX * Math.PI / 180
					this.rX = this.dragY * Math.PI / 180
				}
			}
			this.windowMouseup = () => {
				if(this.draggable){
					this.isPress = false
				}
			}
			this.windowMousewheel = event => {
				if(this.draggable){
					if(event.deltaY){
						this.dis += event.deltaY * this.zoomQuotiety
					}else{ // 兼容火狐
						this.dis += event.detail * 33 * this.zoomQuotiety
					}
				}
			}
			this.windowResize = () => {
				this.mainWidth = w.innerWidth
				this.mainHeight = w.innerHeight
				this.canvas.setAttribute("width",this.mainWidth)
				this.canvas.setAttribute("height",this.mainHeight)
			}
			w.addEventListener("mousedown",this.windowMousedown)
			w.addEventListener("mousemove",this.windowMousemove)
			w.addEventListener("mouseup",this.windowMouseup)
			w.addEventListener("mousewheel",this.windowMousewheel)
			w.addEventListener("DOMMouseScroll",this.windowMousewheel)
			w.addEventListener("resize",this.windowResize)
		},
		destroy(){
			// 解绑所有事件
			w.removeEventListener("mousedown",this.windowMousedown)
			w.removeEventListener("mousemove",this.windowMousemove)
			w.removeEventListener("mouseup",this.windowMouseup)
			w.removeEventListener("mousewheel",this.windowMousewheel)
			w.removeEventListener("DOMMouseScroll",this.windowMousewheel)
			w.removeEventListener("resize",this.windowResize)
			// 清除计时器
			clearInterval(this.timer)
			clearInterval(this.cTimer)
			this.cx.clearRect(0,0,this.mainWidth,this.mainHeight)
			// 解除 canvas dom 占用
			this.canvas.isUsed = false
		},
		emit() {
			this["mode"+this.mode]()
		},
		mode1(){
			this.f = 800 // 焦距
			this.dis = 1600 // 视距
			this.zoomQuotiety = 1
			// this.dragY = this.rX * 180 / Math.PI
			this.showTail = true
			;(e.body || e.documentElement).style.backgroundImage = "linear-gradient(#0b1330 0,#364578 100%)"
			// 创建粒子
			for (var i = 0; i < 10; i++) {
				for (var j = 0; j < 10; j++) {
					this.pList.push({
						pos: [(i - 5) * 120, (j - 5) * 80, (Math.random() - .5) * 1200], // 粒子三维坐标
						size: Math.random() * 10 + 2, // 粒子尺寸
						color: "hsl("+Math.floor(Math.random()*360)+",100%,"+(Math.floor(Math.random()*15)+85)+"%)", // 粒子颜色
						age: 0 // 粒子年龄
					})
				}
			}
			// 绘制
			this.draw(()=>{
				this.pList.forEach(p => {
					if(!this.isPress){
						this.rY = this.ms / 1000
						this.rX = -0.3
					}
					this.cx.save()
					var p2d = this.deDiv(p.pos)
					if(p2d.mul >= 0){
						this.cx.fillStyle = p.color
						this.cx.shadowBlur = 30
						this.cx.shadowColor = p.color
						this.cx.beginPath()
						this.cx.arc(...p2d.res, p.size * p2d.mul,0,Math.PI*2)
						this.cx.fill()
					}
					this.cx.restore()
				})
			})
		},
		draw(frame,isStatic,isFromConner){
			var t = this.showTail ? 10 : 16
			if(isStatic){
				frame.call(this)
			}else{
				this.timer = setInterval(() => {
					this.ms = performance.now()
					this.cx.save()
					!this.showTail && this.cx.clearRect(0,0,this.mainWidth,this.mainHeight)
					this.showTail && (this.cx.fillStyle = "#00000011",this.cx.fillRect(0,0,this.mainWidth,this.mainHeight))
					if(!isFromConner){
						this.cx.translate(this.mainWidth / 2, this.mainHeight / 2)
					}
					frame()
					this.cx.restore()
				}, 16)
			}
		},
		deDiv(p3d) {
			var res = this.rotateX(...this.rotateY(...p3d, this.rY), this.rX)
			// var res = this.rotateY(...this.rotateX(...p3d, this.rX), this.rY)
			var mul = this.f / (this.dis - res[2] - this.f) // 计算透视缩放系数
			// var mul = .4-this.dis / 80000 // 平行透视
			return {
				res: [res[0] * mul, res[1] * mul],
				mul,
				origin: res
			}
		},
		rotateY(x, y, z, a) {
			// 绕 Y 轴旋转角度 a
			var x1 = x,
				z1 = z;
			x1 = x * Math.cos(a) - z * Math.sin(a)
			z1 = z * Math.cos(a) + x * Math.sin(a)
			return [x1, y, z1]
		},
		rotateX(x, y, z, a) {
			// 绕 X 轴旋转角度 a
			var y1 = y,
				z1 = z;
			y1 = y * Math.cos(a) - z * Math.sin(a)
			z1 = z * Math.cos(a) + y * Math.sin(a)
			return [x, y1, z1]
		},
		getNVectorByPoints(p1,p2,p3){
			// 计算由 p1,p2,p3 三点决定平面的 法向量
			var v1 = [p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2]]
			var v2 = [p3[0] - p1[0], p3[1] - p1[1], p3[2] - p1[2]]
			return this.getCross(v1,v2)
			// return [v1[1]*v2[2]-v2[1]*v1[2], v2[0]*v1[2]-v1[0]*v2[2], v1[0]*v2[1]-v2[0]*v1[1]]
		},
		getCross(v1,v2){// 计算 v1,v2 两个向量的叉积
			return [v1[1]*v2[2]-v2[1]*v1[2], v2[0]*v1[2]-v1[0]*v2[2], v1[0]*v2[1]-v2[0]*v1[1]]
		},
		getVectorCos(v1,v2){
			// 计算两向量夹角余弦值
			var a = v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]
			var b = Math.sqrt((Math.pow(v1[0],2)+Math.pow(v1[1],2)+Math.pow(v1[2],2)) * (Math.pow(v2[0],2)+Math.pow(v2[1],2)+Math.pow(v2[2],2)))
			return b===0 ? 0 : a / b < 0 ? 0 : a / b
		},
		// 索引循环
		iCir(tempI,length){
			while(tempI < 0){
				tempI += length
			}
			return tempI % length
		}
	})
	w.Particle = Particle
}(document,window)
</script>
<script type="text/javascript">
!function(w,e){
	function Game(option){
		Object.assign(this,{
			el: "#gameCanvas",
			mainWidth: 400,
			mainHeight: 400,
			timer: null,
			wTimer: null,
			pos: [0,0],
			axis: [0,0],
			side: 20,
			foodSize: 6,
			foodOffset: 7,
			bodySize: 10,
			bodyOffset: 4,
			headSize: 20,
			nX: 32,
			nY: 16,
			mainDirection: [1,0], // x,y
			direList: [], // 操作队列
			bodyList: [],
			foodList: [],
			headColor: "#ff8277",
			bodyColor: "#ffbab7",
			stepColor: "#ffd7c9",
			progress: 0,
			score: 0,
			isPause: false,
			isStart: false,
			isRestart: false
		},option)
		this.init()
	}
	Game.prototype.expend = function(obj){Object.assign(this,obj)}
	Game.prototype.expend({
		// 初始化
		init(){
			this.canvas = e.querySelector(this.el)
			this.canvas.isUsed = true
			this.mainWidth = this.side*this.nX
			this.mainHeight = this.side*this.nY
			this.canvas.setAttribute("width",this.mainWidth)
			this.canvas.setAttribute("height",this.mainHeight)
			this.cx = this.canvas.getContext("2d")
			this.bodyList = [[Math.floor(this.nX/2),Math.floor(this.nY/2)], [Math.floor(this.nX/2)-1,Math.floor(this.nY/2)]]
			this.foodList.num = 0
			this.foodOffset = (this.side - this.foodSize) / 2
			if(this.bodySize > this.side - 2){
				this.bodySize = this.side - 2
			}
			this.bodyOffset = (this.side - this.bodySize) / 2
			for(let i = 0; i < this.nY; i++){
				this.foodList[i] = []
				for(let j = 0; j < this.nX; j++){
					this.foodList[i][j] = {
						color: `hsl(${Math.floor(Math.random()*360)},50%,80%)`,
						appear: false
					}
				}
			}
			this.addEvents()
			if(this.isRestart){
				this.start()
			}else{
				this.load()
			}
		},
		// 绑定事件
		addEvents(){
			this.windowKeydown = evt => {
				var k = evt.key
				if(k === "a" || k === "A" || k === "ArrowLeft"){
					this.turn(-1,0)
				}else if(k === "d" || k === "D" || k === "ArrowRight"){
					this.turn(1,0)
				}else if(k === "w" || k === "W" || k === "ArrowUp"){
					this.turn(0,-1)
				}else if(k === "s" || k === "S" || k === "ArrowDown"){
					this.turn(0,1)
				}
			}
			w.addEventListener("keydown",this.windowKeydown)
		},
		// 开始游戏
		start(){
			this.isStart = true
			this.makeFood()
			this.wTimer = setInterval(() => {
				this.walk()
			},200)
			this.draw(() => {
				this.cx.fillStyle = "#000"
				this.cx.fillRect(0,0,this.mainWidth,this.mainHeight)
				this.foodShader()
				this.bodyShader()
			})
		},
		// 加载界面
		load(){
			this.cx.font = "24px 微软雅黑"
			this.cx.textAlign = "center"
			this.cx.textBaseline = "middle"
			this.draw(() => {
				if(this.progress < 100){
					this.progress += 2
				}else if(this.progress < 200){
					this.progress ++
				}else if(this.progress < 292){
					this.progress += 3
				}else{
					clearInterval(this.timer)
					setTimeout(() => {
						this.progress = 0
						this.welcome()
					},500)
				}
				this.cx.fillStyle = "#000"
				this.cx.fillRect(0,0,this.mainWidth,this.mainHeight)
				this.cx.fillStyle = "#cbcbcb"
				this.cx.fillRect(this.mainWidth/2-150,this.mainHeight/2-10,300,20)
				this.cx.fillStyle = "#5a5a5a"
				this.cx.fillRect(this.mainWidth/2-146,this.mainHeight/2-6,292,12)
				this.cx.fillStyle = "#80e8ff"
				this.cx.fillRect(this.mainWidth/2-146,this.mainHeight/2-6,this.progress,12)
				this.cx.fillText("加载中......",this.mainWidth/2,this.mainHeight/2+50)
			})
		},
		// 欢迎界面
		welcome(){
			this.draw(() => {
				this.cx.fillStyle = "#000000"
				this.cx.fillRect(0,0,this.mainWidth,this.mainHeight)
				this.cx.fillStyle = "#80e8ff"
				this.cx.fillText("欢迎游玩贪吃蛇！",this.mainWidth/2,this.mainHeight/2)
				setTimeout(()=>{
					this.start()
				},2000)
			},true)
		},
		// 销毁实例
		destroy(){
			this.timer && clearInterval(this.timer)
			this.wTimer && clearInterval(this.wTimer)
			this.cx.clearRect(0,0,this.mainWidth,this.mainHeight)
			this.canvas.isUsed = false
			w.removeEventListener("keydown",this.windowKeydown)
		},
		// 暂停
		pause(){
			if(this.isStart){
				if(this.isPause){
					this.isPause = false
					this.wTimer = setInterval(() => {
						this.walk()
					},200)
				}else{
					this.isPause = true
					this.wTimer && clearInterval(this.wTimer)
				}
			}
			
			
		},
		// 主绘制函数
		draw(frame,isSingle){
			this.timer && clearInterval(this.timer)
			var main = () => {
				this.cx.save()
				this.cx.clearRect(0,0,this.mainWidth,this.mainHeight)
				frame()
				this.cx.restore()
			}
			if(isSingle){
				main()
			}else{
				this.timer = setInterval(main,16)
			}
		},
		// 渲染身体
		bodyShader(){
			let p = this.bodyList
			for(let i = 0; i < this.bodyList.length; i++){
				// 绘制基本格子
				if(i === 0){
					this.cx.fillStyle = this.headColor
					this.cx.beginPath()
					this.cx.arc((p[i][0]+.5)*this.side,(p[i][1]+.5)*this.side,this.headSize/2,0,Math.PI*2)
					this.cx.closePath()
					this.cx.fill()
				}else{
					this.cx.fillStyle = this.bodyColor
					this.cx.fillRect(p[i][0]*this.side+this.bodyOffset,p[i][1]*this.side+this.bodyOffset,this.bodySize,this.bodySize)
				}
				// 绘制连接处
				let list = []
				if(i !== 0){
					list.push([p[i-1][0] - p[i][0],p[i-1][1] - p[i][1]])
				}
				if(i !== this.bodyList.length - 1){
					list.push([p[i+1][0] - p[i][0],p[i+1][1] - p[i][1]])
				}
				let a = this.bodySize
				let b = this.bodyOffset
				list.forEach(l => {
					this.cx.fillStyle = this.stepColor
					this.cx.fillRect(
						(p[i][0]+.5)*this.side+(a+b)/2*l[0]-(b-a)/2*Math.abs(l[0])-a/2,
						(p[i][1]+.5)*this.side+(a+b)/2*l[1]-(b-a)/2*Math.abs(l[1])-a/2,
						a+Math.abs(l[0])*(b-a),
						a+Math.abs(l[1])*(b-a)
					)
				})
			}
		},
		// 渲染食物
		foodShader(){
			for(let i = 0; i < this.nY; i++){
				for(let j = 0; j < this.nX; j++){
					let f = this.foodList[i][j]
					if(f.appear){
						this.cx.fillStyle = f.color
						this.cx.fillRect(this.side * j + this.foodOffset, this.side * i + this.foodOffset, this.foodSize, this.foodSize)
					}
				}
			}
		},
		// 走一步
		walk(){
			if(this.direList.length !== 0){
				this.mainDirection = this.direList.shift()
			}
			var headNode = this.bodyList[0]
			var newHead = [headNode[0] + this.mainDirection[0],headNode[1] + this.mainDirection[1]]
			// 遍历身体数组, 查找交集
			var isOver = false
			for(let i = 0; i < this.bodyList.length; i++){
				let b = this.bodyList[i]
				if(newHead[0] === b[0] && newHead[1] === b[1]){
					isOver = true
					break
				}
			}
			if(newHead[0] >= this.nX || newHead[0] < 0 || newHead[1] >= this.nY || newHead[1] < 0){
				isOver = true
			}
			if(!isOver){
				this.bodyList.unshift(newHead)
				// 检查是否吃到食物
				if(this.foodList[newHead[1]][newHead[0]].appear){
					this.foodList[newHead[1]][newHead[0]].appear = false
					this.foodList.num--;
					this.score += 68
					this.upDateScore(this.score)
					this.makeFood()
				}else{
					this.bodyList.pop()
				}
			}else{
				this.handleOver()
			}
		},
		upDateScore(){},
		// 转向
		turn(x,y){
			if(!this.isPause){
				if(this.mainDirection[0] * x < 0 || this.mainDirection[1] * y < 0){
					return false
				}
				this.direList.push([x,y])
				if(this.direList.length > 1){
					this.direList.pop()
				}
			}
		},
		// 制作一次食物
		makeFood(){
			while(this.foodList.num < 20){
				let i = Math.floor(Math.random() * this.nY)
				let j = Math.floor(Math.random() * this.nX)
				if(!this.foodList[i][j].appear){
					this.foodList[i][j].appear = true
					this.foodList.num++
				}
			}
		},
		// 游戏结束回调
		handleOver(){
			this.msg("game over!!!")
			clearInterval(this.wTimer)
			this.wTimer = null
			this.remainRecord(this.score)
		},
		remainRecord(){},
		msg(){}
		
	})
	w.Game = Game
}(window,document)
var gameDemo
start()
function showRecord () {
	// 展示记录
	maskEl.style.display = maskEl.style.display == 'block' ? 'none' : 'block'
}
function start (isRestart) {
	// 开始
	cmd.innerText = "开始"
	gameDemo && gameDemo.destroy()
	gameDemo = new Game({
		isRestart,
		upDateScore(sc){
			score.innerText = sc
		},
		remainRecord(rec){
			records.unshift(rec)
			var str = ""
			records.forEach(r => {
				str += `<div>${ r }分</div>`
			})
			record.innerHTML = str
		},
		msg(msg){
			cmd.innerText = msg
		}
	})
	score.innerText = "0"
}
function pause () {
	// 暂停
	gameDemo.pause()
	gameDemo.isPause ? (pauseBtn.innerText = '继续',cmd.innerText = "暂停") : (pauseBtn.innerText = '暂停',cmd.innerText = "继续")
	
}
maskEl.onclick = showRecord
var records = []
var pDemo = new Particle({
	mode: 1,
	draggable: false
})
	
	
</script>
</body>
</html>